package wator.model;

/**
 * This class represents an integer vector. Implementing subclasses contain
 * realizations for specific dimensions. This interface provides for the 
 * implementation of a variety of algebraic operations, such as addition or 
 * multiplication. Design principles for these operators allow no modifications
 * of the called instance. Instead, a new result instance should be created. 
 * For example, a + b = c is translated to c = a.add(b), where a and b remain
 * unmodified.
 */
abstract public class IntegerVector implements Comparable<IntegerVector> {
	
	/**
	 * Get the dimension of the vector. This should be a fixed value for each
	 * implementing subclass.
	 * 
	 * @return the dimension of the vector
	 */
	abstract public int getDimension();
	
	/**
	 * Scalar multiplication.
	 * 
	 * @param scalar the scalar
	 * @return the scalar multiplied vector
	 */
	abstract public IntegerVector mul(int scalar);
	
	/**
	 * Scalar multiplication with a double value. Ceiling is done after
	 * the entire calculation took place.
	 * 
	 * @param scalar the scalar
	 * @return the scalar multiplied vector
	 */
	abstract public IntegerVector mul(double scalar);
	
	/**
	 * Scalar multiplication formulated as division. The called vector
	 * is multiplied by <code>1/scalar</code> and rounded.
	 * 
	 * @param scalar the divisor
	 * @return the vector multiplied by <code>1/scalar</code>
	 */
	abstract public IntegerVector div(int scalar);
	
	/**
	 * This is pairwise vector multiplication. In 2 dimensional case, this is
	 * (a b) * (c d) = (a*c b*d). The argument has to have the same dimension
	 * as the called instance.
	 * 
	 * @param v2 the multiplicator
	 * @return the result of the pairwise multiplication
	 * @throws NullPointerException if v2 is null
	 * @throws ClassCastException if v2 does not have the same dimension
	 */
	abstract public IntegerVector mul(IntegerVector v2);
	
	/**
	 * This is pairwise vector addition. In 2 dimensional case, this is
	 * (a b) + (c d) = (a+c b+d). The argument has to have the same dimension
	 * as the called instance.
	 * 
	 * @param v2 the additive vector 
	 * @return the result of the pairwise addition
	 * @throws NullPointerException if v2 is null
	 * @throws ClassCastException if v2 does not have the same dimension
	 */
	abstract public IntegerVector add(IntegerVector v2);
	
	/**
	 * This is a wrapper method for <code>this.add(v2.mul(-1))</code>.
	 * 
	 * @param v2 the subtract 
	 * @return the result of the pairwise subtraction
	 * @throws NullPointerException if v2 is null
	 * @throws ClassCastException if v2 does not have the same dimension
	 */
	abstract public IntegerVector sub(IntegerVector v2);
	
	/**
	 * This method applies the modulo operator pairwise on the elements of the
	 * vector. In 2 dimensional case, this can be written as is
	 * (a b) % (c d) = (a%c b%d). The argument has to have the same dimension
	 * as the called instance.
	 * 
	 * @param v2 a vector containing the divisors
	 * @return the result of the pairwise modulo operation
	 * @throws NullPointerException if v2 is null
	 * @throws ClassCastException if v2 does not have the same dimension
	 */
	abstract public IntegerVector mod(IntegerVector v2);
	
	/**
	 * This method should return a randomly generated vector. The elements in
	 * the created vector should not exceed the limits defined by the called
	 * vector instance, but must not be negative.
	 * 
	 * @return a random vector,  which is pairwise greater or equal 0, but 
	 * 		lower than the called instance
	 */
	abstract public IntegerVector generateRandomVectorInRange();
	
	/**
	 * This method returns the greatest element in the vector.
	 * 
	 * @return the greatest element in the vector (scalar)
	 */
	abstract public int max();
	
	/**
	 * This method returns the smallest element in the vector.
	 * 
	 * @return the smallest element in the vector (scalar)
	 */
	abstract public int min();
	
	/**
	 * The euclidean distance to another vector. This should not be overridden
	 * since this implementation makes use of {@link #euclideanDistance2}.
	 * 
	 * @param v2 the second vector
	 * @return the euclidean distance between v2 and this vector
	 * @throws ClassCastException if v2 has not the right dimension
	 * @throws NullPointerException if <code>v2</code> is null
	 */
	public double euclideanDistance(IntegerVector v2) {
		return Math.sqrt(euclideanDistance2(v2));
	}
	
	/**
	 * The euclidean distance to another vector power two. Note that quadration
	 * does not change any ordering based on the euclidean distance, but
	 * reduces computational complexity since it avoids taking the square
	 * root.
	 * 
	 * @param v2 the second vector
	 * @return the euclidean distance between v2 and this vector
	 * @throws ClassCastException if v2 has not the right dimension
	 * @throws NullPointerException if <code>v2</code> is null
	 */
	abstract public int euclideanDistance2(IntegerVector v2);
	
	/**
	 * The length of the vector. This is the euclidean distance to
	 * the null vector, so it should be consistent with 
	 * {@link #euclideanDistance}. Please do not override this method
	 * since this implementation makes use of the abstract {@link #length2}.
	 * 
	 * @return the length of the vector
	 * @see #euclideanDistance(IntegerVector)
	 */
	public double length() {
		return Math.sqrt(length2());
	}
	
	/**
	 * The length of the vector power two. This is the euclidean distance to
	 * the null vector, so it should be consistent with 
	 * {@link #euclideanDistance2}. n the euclidean distance, but
	 * reduces computational complexity since it avoids taking the square
	 * root.
	 * 
	 * @return the length of the vector
	 * @see #euclideanDistance2(IntegerVector)
	 */
	abstract public int length2();
	
	/**
	 * Get the volume induced by the vector. This is the number of possible 
	 * vectors  between this vector and the null vector, assuming this is a
	 * size vector. The result should be the same as the dot product with this
	 * vector and 1.
	 * 
	 * @return the "volume" induced by the vector
	 */
	abstract public int vol();
	
	/**
	 * This method tries to reduce the length of the vector, without changing
	 * its end point on a torus topology. In other words, it checks if it would
	 * be shorter to go the other way around on the torus and returns the 
	 * shortest path to the original end point. For example, this is used to
	 * determine the correct distance of an individual on the left and right
	 * border, since the distance vector in this case crosses the universe
	 * borders.
	 * 
	 * @param sizeVector the size vector of the torus topology
	 * @return the shortest vector with the same end point as this vector
	 * @throws NullPointerException if <code>sizeVector</code> is null
	 * @throws ClassCastException if <code>sizeVector</code> has not the same 
	 * 			dimension as this vector
	 */
	abstract public IntegerVector reduce(IntegerVector sizeVector);
	
	/**
	 * This gives you a vector with the given dimension and the given value
	 * in all its dimension.
	 * 
	 * @param dim the required dimension. Must not exceed
	 * 			{@link Constants#MAX_DIM}.
	 * @return a cube vector with dimension <code>dim</code> and all elements
	 * 			equal to <code>val</code> or NULL if dim is not in the feasible
	 * 			range
	 * @see Constants#MAX_DIM
	 */
	public static IntegerVector getCubeVector(int dim, int l) {
		assert dim > 0 && dim <= Constants.MAX_DIM;
		switch (dim) {
		case 1:
			return new Integer1DVector(l);
		case 2:
			return new Integer2DVector(l, l);
		case 3:
			return new Integer3DVector(l, l, l);
		}
		return null;
	}
	
	/**
	 * This static method returns the default vector with the given dimension.
	 * This is equal to the default size of the wator universe. Note that this
	 * can be seen as a wrapper method for <code>getCubeVector(dim, Constants.
	 * DEFAULT_VECTOR_LENGTH)</code>. 
	 *  
	 * @param dim the required dimension. Must not exceed
	 * 			{@link Constants#MAX_DIM}.
	 * @return a default vector with dimension <code>dim</code> or NULL if dim
	 * 			is not in the feasible range
	 * @see Constants#DEFAULT_VECTOR_LENGTH
	 * @see Constants#MAX_DIM
	 */
	public static IntegerVector getDefaultVector(int dim) {
		return getCubeVector(dim, Constants.DEFAULT_VECTOR_LENGTH);
	}

	/**
	 * Vectors are ordered by their length. If two vectors have the same 
	 * length, the values are compared dimension by dimension.
	 * {@inheritDoc}
	 */
	@Override
	abstract public int compareTo(IntegerVector arg0);
}
